package com.caigou.service.impl;

import com.caigou.bean.bid.entity.Region;
import com.caigou.bean.es.EsBid;
import com.caigou.bean.param.EsBidParam;
import com.caigou.bean.param.RegionParam;
import com.caigou.bean.vo.ItemVO;
import com.caigou.repository.EsBidRepository;
import com.caigou.service.IBidService;
import com.caigou.service.IEsBidService;
import com.caigou.util.DateUtil;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.*;


@Service
@Slf4j
public class EsBidServiceImpl implements IEsBidService {
    @Autowired
    @Qualifier("esBidRepository")
    private EsBidRepository esBidRepository;
    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;


    @Resource
    private IBidService bidService;

    @Override
    public Optional<EsBid> findById(String id) {
        return esBidRepository.findById(id);
    }

    @Override
    public Iterable<EsBid> findAllById(Iterable<String> ids) {
        return null;
    }

    @Override
    public EsBid save(EsBid esBid) {
        return null;
    }

    @Override
    public void delete(EsBid esBid) {

    }

    @Override
    public Page<EsBid> findByTitle(String title, Pageable pageable) {
        return null;
    }

    @Override
    public Page<EsBid> queryBid(String queryText, String startTime, String endTime, BigDecimal lowBidMoney, BigDecimal highBidMoney, Pageable pageable) {
        return null;
    }

    @Override
    public Page<EsBid> queryBidByIds(List<Long> bidIds) {
        return null;
    }

    @Override
    public Page<EsBid> queryBidTitle(EsBidParam param) {
        PageRequest page = PageRequest.of(param.getPageNum(), param.getPageSize());

        //1.创建QueryBuilder(即设置查询条件)这儿创建的是组合查询(也叫多条件查询),后面会介绍更多的查询方法
        /*组合查询BoolQueryBuilder
         * must(QueryBuilders)   :AND
         * mustNot(QueryBuilders):NOT
         * should:               :OR
         */
        //builder下有must、should以及mustNot 相当于sql中的and、or以及not
        BoolQueryBuilder builder = QueryBuilders.boolQuery();

        //设置精确搜索,标讯信息中的title含有text
//        BoolQueryBuilder titleBuilder = QueryBuilders.boolQuery();
//        titleBuilder.should(QueryBuilders.matchQuery("title", param.getQueryText()).boost(3))
//                    .should(QueryBuilders.matchQuery("title.ik", param.getQueryText()).boost(2))
//                    .should(QueryBuilders.matchQuery("title.pinyin", param.getQueryText()).boost(2));
//        builder.must(titleBuilder);//boost默认为1

//        BoolQueryBuilder titleBuilder = QueryBuilders.boolQuery();
//        titleBuilder.should(QueryBuilders.matchQuery("title", param.getQueryText()).operator(Operator.AND).boost(3))
//                    .should(QueryBuilders.matchQuery("titlePinYin", param.getQueryText()).operator(Operator.AND));
//        builder.must(titleBuilder);

        //是否包含中文的正则表达式判断
        //TODO 英文或者拼音搜索暂时不用  后续看业务需求
        //Pattern pattern = Pattern.compile("[\u4E00-\u9FA5]");
        //Matcher matcher = pattern.matcher(param.getQueryText());
        //if (matcher.find()) {//包含中文
        if (true) {//包含中文
            //标题汉字搜索
            if (param.getQueryText() != null && !StringUtils.isEmpty(param.getQueryText())) {
                builder.must(QueryBuilders.matchQuery("title", param.getQueryText()).operator(Operator.AND).boost(3));//boost默认为1
                log.info("中文汉字搜索-->" + param.getQueryText());
            }

        } else {//纯拼音或者英文
            if (param.getQueryText() != null && !StringUtils.isEmpty(param.getQueryText())) {
                BoolQueryBuilder titleBuilder = QueryBuilders.boolQuery();
                titleBuilder.should(QueryBuilders.matchQuery("title", param.getQueryText()).operator(Operator.AND).boost(3))
                        .should(QueryBuilders.matchQuery("titlePinYin", param.getQueryText()).operator(Operator.AND));
                builder.must(titleBuilder);
                log.info("中文拼音搜索-->" + param.getQueryText());
            }

        }


        //拼音搜索
        //builder.must(QueryBuilders.matchQuery("titlePinYin", param.getQueryText()).operator(Operator.AND).boost(2));
        //builder.must(QueryBuilders.matchQuery("title", param.getQueryText()).operator(Operator.AND).boost(3));//operator默认为or
//        //设置精确搜索，标讯信息的projectName精确匹配text,，也可以模糊匹配QueryBuilders.fuzzyQuery(name, value)
//        builder.must(QueryBuilders.matchQuery("projectName", param.getQueryText()));

        //可以同时设置多个字段的搜索，第一个参数是查询的值，后面的参数是字段名，可以跟多个字段，用逗号隔开
//        builder.must(QueryBuilders.multiMatchQuery(param.getQueryText(), "title", "projectName"));

        //设置精确搜索，对标讯简介内容
        //builder.must(QueryBuilders.matchQuery("projectInfo", param.getQueryText()));
        //builder.must(QueryBuilders.matchQuery("projectInfo", param.getQueryText()).operator(Operator.AND));

        //对品类进行精确搜索
        //builder.must(QueryBuilders.matchQuery("item", param.getQueryText())).boost(2);

        //对采购人进行精确搜索
//        if(param.getPurchaser()!=null) {
//            builder.must(QueryBuilders.matchQuery("purchaser", param.getPurchaser()));
//        }

        //构造搜索条件
        this.setQueryBuilder(builder, param);


        SortBuilder sort = null;
        if (param.getSort().intValue() == 1) {//相似度排序
            sort = SortBuilders.scoreSort().order(SortOrder.DESC);
        } else if (param.getSort().intValue() == 2) {//项目时间排序
            sort = SortBuilders.fieldSort("projectDate").order(SortOrder.DESC);
        } else if (param.getSort().intValue() == 3) {//标的金额排序
            sort = SortBuilders.fieldSort("bidMoney").order(SortOrder.DESC);
        } else if (param.getSort().intValue() == 4) {//预算金额排序
            sort = SortBuilders.fieldSort("budgetMoney").order(SortOrder.DESC);
        } else if (param.getSort().intValue() == 5) {//id倒序
            sort = SortBuilders.fieldSort("_id").order(SortOrder.DESC);
        } else {
            sort = SortBuilders.scoreSort().order(SortOrder.DESC);
        }
        FieldSortBuilder id = SortBuilders.fieldSort("_id").order(SortOrder.DESC);
        //2.构建查询
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        //将搜索条件设置到构建中
        nativeSearchQueryBuilder.withQuery(builder);
        //将分页设置到构建中
        nativeSearchQueryBuilder.withPageable(page);
        //将排序设置到构建中
        //nativeSearchQueryBuilder.withSort(sort).withSort(sort2);
        nativeSearchQueryBuilder.withSort(sort).withSort(id);
        //设置高亮显示
        //withHighlightFields(new HighlightBuilder.Field(field))
        HighlightBuilder.Field highlight1 = new HighlightBuilder.Field("title");
        highlight1.preTags("<span class='high-light'>").postTags("</span>");//红色高亮标签
        //Field highlight2 = new HighlightBuilder.Field("projectName");
        //nativeSearchQueryBuilder.withHighlightFields(highlight1, highlight2);
        nativeSearchQueryBuilder.withHighlightFields(highlight1);
        //设置返回字段
//        String[] includes = null;//{"title", "projectName"};
        String[] excludes = {"pureContent", "content"};
        FetchSourceFilter fetchSourceFilter = new FetchSourceFilter(null, excludes);
        nativeSearchQueryBuilder.withSourceFilter(fetchSourceFilter);
//        nativeSearchQueryBuilder.withFields("title");
        //生成NativeSearchQuery
        NativeSearchQuery query = nativeSearchQueryBuilder.build();

//        //es搜索指定返回字段
//        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//        searchSourceBuilder.fetchSource(new String[]{"title", "projectDate"}, new String[]{"pureContent", "content"});

        //3.执行方法
        //Page<EsBid> esBidPage = esBidRepository.search(query);
//        Page<EsBid> esBidPage = elasticsearchTemplate.queryForPage(query, EsBid.class);

        //3.高亮显示的执行方法，实施查询，注意：这里的泛型最后和 elasticsearch 中的字段对应
        AggregatedPage<EsBid> esBidPage = elasticsearchTemplate.queryForPage(query, EsBid.class, new SearchResultMapper() {
            @Override
            public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
                List<T> esBidList = new ArrayList<>();
                //命中记录
                SearchHits hits = response.getHits();
                for (SearchHit hit : hits) {
                    if (hits.totalHits <= 0) {
                        return null;
                    }

                    Map<String, Object> hitMap = hit.getSourceAsMap();
                    String id = hit.getId();
                    EsBid esBid = createEsBid(id, hitMap, false);

                    //设置高亮（若对应字段有高亮的话）
                    setHighLight(hit, "title", esBid);
                    //setHighLight(hit, "projectName", esBid);

                    esBidList.add((T) esBid);
                }
                return new AggregatedPageImpl<>((List<T>) esBidList, pageable, response.getHits().getTotalHits());

            }
        });
        return esBidPage;
    }


    /**
     * set 设置查询条件
     *
     * @param builder
     * @param param
     */
    private void setQueryBuilder(BoolQueryBuilder builder, EsBidParam param) {
        //采购类型查询
        if (param.getPurchaseTypeNameList() != null && param.getPurchaseTypeNameList().size() > 0) {
            BoolQueryBuilder purchaseBuilder = QueryBuilders.boolQuery();
            for (String pname : param.getPurchaseTypeNameList()) {
                purchaseBuilder.should(QueryBuilders.matchQuery("purchaseTypeName", pname));
            }
            builder.must(purchaseBuilder);
        }

        //公告类型查询
        if (param.getAnnouncementTypeNameList() != null && param.getAnnouncementTypeNameList().size() > 0) {
            BoolQueryBuilder announcementBuilder = QueryBuilders.boolQuery();
            for (String aname : param.getAnnouncementTypeNameList()) {
                announcementBuilder.should(QueryBuilders.matchQuery("announcementTypeName", aname));
            }
            builder.must(announcementBuilder);
        }
//        else {
//            BoolQueryBuilder purchaseBuilder = QueryBuilders.boolQuery();
//            Map<Integer, String> announcementDic = dictionaryService.findAnnouncementDic();
//            announcementDic.forEach((key,value)->{
//                purchaseBuilder.should(QueryBuilders.matchQuery("announcementTypeName", value));
//            });
//            builder.must(purchaseBuilder);
//        }


        //第一个参数是查询的值，后面的参数是字段名，可以跟多个字段，用逗号隔开
        //QueryBuilder queryBuilder = QueryBuilders.multiMatchQuery("changge", "address","interests");

        //时间范围的设定
        if (param.getStartTime() != null) {
            builder.must(QueryBuilders.rangeQuery("projectDate").from(param.getStartTime()));
        }
        if (param.getEndTime() != null) {
            builder.must(QueryBuilders.rangeQuery("projectDate").to(param.getEndTime()));
        }

        //价格范围的设定--标的金额
//        if(param.getLowBidMoney()!=null) {
//            builder.must(QueryBuilders.rangeQuery("bidMoney").from(param.getLowBidMoney()));
//        }
//        if(param.getHighBidMoney()!=null) {
//            builder.must(QueryBuilders.rangeQuery("bidMoney").to(param.getHighBidMoney()));
//        }

        //价格范围的设定--预算金额
//          if(param.getLowBidMoney()!=null) {
//              builder.must(QueryBuilders.rangeQuery("budgetMoney").from(param.getLowBidMoney()));
//          }
//          if(param.getHighBidMoney()!=null) {
//              builder.must(QueryBuilders.rangeQuery("budgetMoney").to(param.getHighBidMoney()));
//          }


        //标的金额、预算金额联合查询
        if (param.getLowBidMoney() != null || param.getHighBidMoney() != null) {
            BoolQueryBuilder moneyBuilder = QueryBuilders.boolQuery();

            BoolQueryBuilder bidMoneyBuilder = QueryBuilders.boolQuery();
            BoolQueryBuilder budgetMoneyBuilder = QueryBuilders.boolQuery();
            if (param.getLowBidMoney() != null) {
                bidMoneyBuilder.must(QueryBuilders.rangeQuery("bidMoney").from(param.getLowBidMoney()));
                budgetMoneyBuilder.must(QueryBuilders.rangeQuery("budgetMoney").from(param.getLowBidMoney()));
            }
            if (param.getHighBidMoney() != null) {
                bidMoneyBuilder.must(QueryBuilders.rangeQuery("bidMoney").to(param.getHighBidMoney()));
                budgetMoneyBuilder.must(QueryBuilders.rangeQuery("budgetMoney").to(param.getHighBidMoney()));
            }

            moneyBuilder.should(bidMoneyBuilder);
            moneyBuilder.should(budgetMoneyBuilder);

            builder.must(moneyBuilder);
        }


//        //项目编号前缀模糊查询
//        String prefix = "010";
//        builder.must(QueryBuilders.wildcardQuery("projectCode", prefix));

        //对地区进行精确查询
        List<RegionParam> regionParamList = param.getRegionList();
        if (regionParamList != null && !regionParamList.isEmpty()) {
            BoolQueryBuilder regionListBuilder = QueryBuilders.boolQuery();
            for (RegionParam regionParam : regionParamList) {
                String province = null;
                String city = null;
                Integer regionLevel = regionParam.getRegionLevel();
                if (regionLevel != null) {
                    if (regionLevel.intValue() == 1) {//省
                        province = regionParam.getRegionName();
                    } else if (regionLevel.intValue() == 2) {//市
                        city = regionParam.getRegionName();
                        String parentCode = regionParam.getParentCode();
                        if (parentCode != null) {
                            Region bidRegion = bidService.getBidRegionByRegionCode(parentCode);
                            if (bidRegion != null) {
                                province = bidRegion.getRegionName();
                            }
                        }
                    }
                }

                //构造regionListBuilder
                if (province != null || city != null) {
                    BoolQueryBuilder regionBuilder = QueryBuilders.boolQuery();
                    if (province != null) {
                        regionBuilder.must(QueryBuilders.matchQuery("province", province));
                    }
                    if (city != null) {
                        regionBuilder.must(QueryBuilders.matchQuery("city", city));
                    }
                    StringBuffer sb = new StringBuffer();
                    if (province != null) {
                        sb.append(province);
                    }
                    if (city != null) {
                        sb.append(city);
                    }
                    if (sb.length() > 0) {
                        regionBuilder.should(QueryBuilders.matchQuery("fullArea", sb.toString()));
                    }
                    regionListBuilder.should(regionBuilder);
                }
            }
            builder.must(regionListBuilder);
        }

        //对content内容进行搜索，采用短语搜索，采用should
        //builder.should(QueryBuilders.matchPhraseQuery("content", param.getQueryText()).slop(2));
        //2级品目编码
        if (param.getLevel2ItemCodeList() != null && param.getLevel2ItemCodeList().size() > 0) {
            log.info("level2ItemCodeList size=" + param.getLevel2ItemCodeList().size());
            BoolQueryBuilder level2ItemCodeBuilder = QueryBuilders.boolQuery();
            for (String level2ItemCode : param.getLevel2ItemCodeList()) {
                log.info("level2ItemCode=" + level2ItemCode);
                level2ItemCodeBuilder.should(QueryBuilders.matchQuery("esItemAis.item1Code", level2ItemCode));
            }
            builder.must(level2ItemCodeBuilder);
        }
    }
    /**
     * createEsBid根据搜索结果构造EsBid对象
     *
     * @param id
     * @param hitMap
     * @return
     */
    private EsBid createEsBid(String id, Map<String, Object> hitMap, boolean includeContent) {
        EsBid esBid = new EsBid();
        esBid.setId(id);

        String title = (String) hitMap.get("title");
        esBid.setTitle(title);

        String projectName = (String) hitMap.get("projectName");
        esBid.setProjectName(projectName);

        String projectCode = (String) hitMap.get("projectCode");
        esBid.setProjectCode(projectCode);

        String strProjectDate = (String) hitMap.get("projectDate");
        Date projectDate = DateUtil.convertString2Date("yyyy-MM-dd HH:mm:ss", strProjectDate);
        esBid.setProjectDate(projectDate);

        String expert = (String) hitMap.get("expert");
        esBid.setExpert(expert);

        String purchaseTypeName = (String) hitMap.get("purchaseTypeName");
        esBid.setPurchaseTypeName(purchaseTypeName);

        String announcementTypeName = (String) hitMap.get("announcementTypeName");
        esBid.setAnnouncementTypeName(announcementTypeName);

        String purchaserName = (String) hitMap.get("purchaserName");
        esBid.setPurchaserName(purchaserName);
//      Integer purchaser = (Integer)hitMap.get("purchaser");
//      esBid.setPurchaser(purchaser);

        String projectInfo = (String) hitMap.get("projectInfo");
        esBid.setProjectInfo(projectInfo);

        Object objBidMoney = hitMap.get("bidMoney");
        if (objBidMoney != null) {
            if (objBidMoney instanceof Integer) {
                Integer bidMoney = (Integer) objBidMoney;
                esBid.setBidMoney(new Long(bidMoney));
            } else if (objBidMoney instanceof Long) {
                Long bidMoney = (Long) objBidMoney;
                esBid.setBidMoney(bidMoney);
            }
        }

        String province = (String) hitMap.get("province");
        esBid.setProvince(province);

        String city = (String) hitMap.get("city");
        esBid.setCity(city);

        String town = (String) hitMap.get("town");
        esBid.setTown(town);

        String fullArea = (String) hitMap.get("fullArea");
        esBid.setFullArea(fullArea);

        String subcontract = (String) hitMap.get("subcontract");
        esBid.setSubcontract(subcontract);

        Object objBudgetMoney = (Object) hitMap.get("budgetMoney");
        if (objBudgetMoney != null) {
            if (objBudgetMoney instanceof Integer) {
                Integer budgetMoney = (Integer) objBudgetMoney;
                esBid.setBudgetMoney(new Long(budgetMoney));
            } else if (objBudgetMoney instanceof Long) {
                Long budgetMoney = (Long) objBudgetMoney;
                esBid.setBudgetMoney(budgetMoney);
            }
        }

        String companyName = (String) hitMap.get("companyName");
        esBid.setCompanyName(companyName);
        String agencyName = (String) hitMap.get("agencyName");
        esBid.setAgencyName(agencyName);


        List<ItemVO> esItemAis = (List<ItemVO>) hitMap.get("esItemAis");
        esBid.setEsItemAis(esItemAis);

        Object objContentId = (Object) hitMap.get("contentId");
        if (objContentId != null) {
            if (objContentId instanceof Integer) {
                Integer contentId = (Integer) objContentId;
                esBid.setContentId(new Long(contentId));
            } else if (objContentId instanceof Long) {
                Long contentId = (Long) objContentId;
                esBid.setContentId(contentId);
            }
        }

        if (includeContent) {
            String pureContent = (String) hitMap.get("pureContent");
            esBid.setPureContent(pureContent);
        }


//      String content = (String)hitMap.get("content");
//      esBid.setContent(content);
        return esBid;
    }
    /**
     * set设置高亮
     *
     * @param hit    命中记录
     * @param
     * @param object 待赋值对象
     */
    private static void setHighLight(SearchHit hit, String field, Object object) {
        //获取对应的高亮域
        Map<String, HighlightField> highlightFields = hit.getHighlightFields();
        HighlightField highlightField = highlightFields.get(field);
        if (highlightField != null) {
            //取得定义的高亮标签
            String highLightMessage = highlightField.fragments()[0].toString();
            // 反射调用set方法将高亮内容设置进去
            try {
                String setMethodName = parseSetMethodName(field);
                Class<?> Clazz = object.getClass();
                Method setMethod = Clazz.getMethod(setMethodName, String.class);
                setMethod.invoke(object, highLightMessage);
            } catch (Exception e) {
                log.error("反射报错", e);
            }
        }
    }

    /**
     * parsetMethodName 根据字段名，获取Set方法名
     *
     * @param fieldName 字段名
     * @return Set方法名
     */
    private static String parseSetMethodName(String fieldName) {
        if (StringUtils.isEmpty(fieldName)) {
            return null;
        }
        int startIndex = 0;
        if (fieldName.charAt(0) == '_') {
            startIndex = 1;
        }
        return "set" + fieldName.substring(startIndex, startIndex + 1).toUpperCase()
                + fieldName.substring(startIndex + 1);
    }
}
